package top.superflow.flow.load


import org.apache.commons.lang3.StringUtils
import top.superflow.common.LocalFlowConstant
import top.superflow.exception.RequestUnsupportedException
import top.superflow.flow.DefaultValues
import top.superflow.flow.config.FlowConfigType._
import top.superflow.flow.config._
import top.superflow.flow.config.process.ProcessConfig
import top.superflow.flow.config.process.provider.ProcessProviderConfig
import top.superflow.flow.config.template.TemplateConfig
import top.superflow.flow.mapper.MapperConfig
import top.superflow.lang.CollectionUtils
import top.superflow.log.Loggable

import scala.collection.immutable
import scala.collection.mutable.{HashMap, Map}

class GlobalFlowConfiguration extends Loggable{

  private val templates = Map[String, Map[String, TemplateConfig]]()

  private val processes = Map[String, Map[String, ProcessConfig]]()

  def getProcesses = toImmutable(processes)

  private val processProviders = Map[String, Map[String, ProcessProviderConfig]]()

  def getProcessProviders = toImmutable(processProviders)

  private def toImmutable[T](mutableNestedMap : Map[String, Map[String, T]]) : immutable.Map[String, immutable.Map[String, T]] = {
    mutableNestedMap.map(entry => (entry._1, entry._2.toMap)).toMap
  }

  private val flows = Map[String, Map[String, FlowConfig]]()

  def getFlows = toImmutable(flows)


  private val mappers = Map[String, Map[String, MapperConfig]]()

  def getMappers = toImmutable(mappers)

  private val resources  = Map[String, Map[String, ResourceConfig]]()

  def getResources = toImmutable(resources)

  def getConfig (configKey:String, configGroup : String, configType : InnerFlowConfigTpe): Option[Config] = {
    val configInGroup = getConfig(configGroup, configType, configKey)
    if(configInGroup == None){
      getConfigInPublic(configType, configKey)
    }else{
      configInGroup
    }
  }

  def getTemplate (templateGroupId : String, templateKey:String): String = {
    val tmpGrp = templates.get(templateGroupId)
    if (tmpGrp == None) {
      return null
    }

    val template = tmpGrp.get.get(templateKey)
    if(template == None){
      return null
    }

    return template.get.getTemplate
  }

  def contains(configKey:String, configGroup : String, configType : InnerFlowConfigTpe) : Boolean = {
    val theConfig = getConfig(configKey, configGroup, configType)

    theConfig != None
  }

  private def getConfigInPublic(configType: InnerFlowConfigTpe, configKey: String) : Option[Config] = {
    getConfig(LocalFlowConstant.LOCAL_FLOW_CONST_PUBLIC_GROUP, configType, configKey)
  }

  private def getConfig(configGroup: String, configType: InnerFlowConfigTpe, configKey: String): Option[Config]  = {
    val groupValue = DefaultValues.getGroup(configGroup)

    configType match {
      case FLOW => {
        GlobalFlowConfiguration.getInstance.getConfig[FlowConfig](configKey, configGroup, flows)
      }
      case PROCESS => {
        GlobalFlowConfiguration.getInstance.getConfig[ProcessConfig](configKey, configGroup, processes)
      }
      case RESOURCE =>{
        GlobalFlowConfiguration.getInstance.getConfig[ResourceConfig](configKey, configGroup, resources)
      }
      case MAPPER =>{
        GlobalFlowConfiguration.getInstance.getConfig[MapperConfig](configKey, configGroup, mappers)
      }
      case _ =>{
        throw new RequestUnsupportedException(configType)
      }
    }
  }

  def add(flowElement : Any){
    flowElement match {
      case processProvider : ProcessProviderConfig =>{
        add[ProcessProviderConfig](processProvider, GlobalFlowConfiguration.getInstance.processProviders)
      }

      case processConfig : ProcessConfig =>{
        add[ProcessConfig](processConfig, GlobalFlowConfiguration.getInstance.processes)
      }

      case resource : ResourceConfig => {
        add[ResourceConfig](resource, GlobalFlowConfiguration.getInstance.resources)
      }

      case mapper : MapperConfig => {
        add[MapperConfig](mapper, GlobalFlowConfiguration.getInstance.mappers)
      }

      case flow : FlowConfig => {
        add[FlowConfig](flow, GlobalFlowConfiguration.getInstance.flows)
      }

      case template : TemplateConfig => {
        add[TemplateConfig](template, GlobalFlowConfiguration.getInstance.templates)
      }

      case _ =>{
        throw new RequestUnsupportedException("The request is not supported: " + flowElement)
      }
    }
  }


  private def add[T <: Identifiable](t : T, cache : Map[String, Map[String, T]]){
    if(cache == null || t == null) return

    val group = DefaultValues.getGroup(t.getGroup)

    val elementsInGroup = getGroup[T](cache, group)

    validate(t, elementsInGroup)

    elementsInGroup.get.put(t.getKey, t)
  }

  def validate[T <: Identifiable](t: T, elementsInGroup: Option[scala.collection.mutable.Map[String,T]]):Unit = {
    if(StringUtils.isBlank(t.getKey)){
      throw new RequestUnsupportedException(t)
    }

    val existingElement = elementsInGroup.get.get(t.getKey)

    if(existingElement == None) return;

    if(StringUtils.equalsIgnoreCase(LocalFlowConstant.LOCAL_FLOW_CONFIG_AUTO_GROUP, existingElement.get.getGroup)) return;

    throw new RequestUnsupportedException("There is already an element with key:" + t.getKey + " in the group: [" + t.getGroup + "], please make sure the key is unique in the group")
  }

  def getGroup[T](cache: Map[String,Map[String,T]], group: String) = {
    var elementsInGroup = cache.get(group)

    if(elementsInGroup == None){
      cache.put(group, new HashMap[String, T]())
      elementsInGroup = cache.get(group)
    }
    elementsInGroup
  }

  private def getConfig[T](configKey : String, groupValue : String, configs : Map[String, Map[String, T]]) : Option[T] = {
    val groupedConfig = configs.get(groupValue)
    if(groupedConfig == None){
      None
    }else{
      val config = groupedConfig.get.get(configKey)
      if(config == None){
        None
      }else{
        Some(config.get.asInstanceOf[T])
      }
    }
  }

  def add(elements : Iterable[_ <: Config]): Unit ={
    if(CollectionUtils.isEmpty(elements)){
      return
    }

    elements.foreach(config => {
      add(config)
    })
  }
}

object GlobalFlowConfiguration {
  private val instance = new GlobalFlowConfiguration()

  def getInstance : GlobalFlowConfiguration = instance
  
  




  
  

}